篇首语:本文由编程笔记#小编为大家整理,主要介绍了Linux-进程控制相关的知识,希望对你有一定的参考价值。
进程的创建:
- 命令行启动命令(程序、指令……)
- 通过程序自身,fork出来的子进程
在linux中fork函数是非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。
语法:
#include
pid_t fork(void);
返回值:自进程中返回0,父进程返回子进程id,出错返回-1
进程调用fork,当控制转移到内核中的fork代码后,内核做:
注:
注:
补: fork创建新进程成功后,系统中出现两个基本完全相同的进程,这两个进程执行没有固定的先后顺序,哪个进程先执行要看系统的进程调度策略。
父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本
注:
补充:
注:
进程退出情况:
注:
总结:
注:
子进程运行,父进程可以关心,也可以不关心子进程的运行结果
进程非正常退出:野指针、/0、越界访问……,退出码是无意义的
注:main函数return,非main函数的return不是终止进程,而是结束进程
注:任何函数exit都表示直接终止进程
注:
结论:
总结:
进程等待必要性:
- 回收僵尸进程,解决内存泄漏
- 需要获取子进程的运行结束状态(有些父进程不需要子进程的运行结束状态)
- 尽量父进程要晚于子进程退出,这样可以规范化进行资源回收(编码角度)
注:进程一旦变成僵尸状态用kill -9 也无能为力,因为没有办法杀死一个已经死去的进程。
#include
#include
pid_t wait(int*status);
返回值:
成功返回被等待进程pid,失败返回-1。
参数:
输出型参数,获取子进程退出状态,不关心则可以设置成为NULL
注:wait方法是等待任意一个子进程,当子进程退出时,wait就可以退出并返回该子进程的pid
循环创建进程以及循环等待:
#include
#include
#include
#include
#include
int main()
int i=0;
while(i<5)
pid_t id&#61;fork();
if(id<0)
perror("fork");
return 1;
if(id&#61;&#61;0)
int count&#61;5;
while(count)
printf("child is runing:%d,ppid:%d,pid:%d\\n",count--,getppid(),getpid());
sleep(1);
printf("child quit.....\\n");
exit(0);
i&#43;&#43;;
for(i&#61;0;i<5;i&#43;&#43;)
printf("father is waiting...\\n");
sleep(10);
pid_t ret&#61;wait(NULL);
printf("father is wait done,ret:%d\\n",ret);
sleep(3);
printf("father quit...\\n");
return 0;
注&#xff1a;一般而言&#xff0c;使用fork函数后需要让父进程进行等待
#include
#include
pid_ t waitpid(pid_t pid, int *status, int options);
返回值&#xff1a;
当正常返回的时候waitpid返回收集到的子进程的进程ID&#xff1b;
如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0&#xff1b;
如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在&#xff1b;
参数&#xff1a;
pid&#xff1a;
Pid&#61;-1,等待任一个子进程。与wait等效。
Pid>0.等待其进程ID与pid相等的子进程。
status:
WIFEXITED(status): 若为正常终止子进程返回的状态&#xff0c;则为真。&#xff08;查看进程是否是正常退出&#xff09;
WEXITSTATUS(status): 若WIFEXITED非零&#xff0c;提取子进程退出码。&#xff08;查看进程的退出码&#xff09;
options:
WNOHANG: 若pid指定的子进程没有结束&#xff0c;则waitpid()函数返回0&#xff0c;不予以等待。若正常结束&#xff0c;则返回该子进程的ID。
注&#xff1a;
注&#xff1a;
补&#xff1a;
pid_t wait(int*status)
pid_ t waitpid(pid_t pid, int *status, int options)
其中的status是输出型参数
注&#xff1a;
注&#xff1a;
终止信号&#xff08;异常&#xff09;&#xff1a;
注&#xff1a;
完整的进程等待&#xff1a;
或者&#xff1a;
注&#xff1a;系统提供了一堆的宏&#xff08;函数&#xff09;&#xff0c;可以用来判断退出码、退出状态
当父进程调用waitpid函数时&#xff0c;父进程等待子进程&#xff0c;直到子进程运行结束&#xff08;退出&#xff09;&#xff0c;在这期间父进程一直等&#xff08;什么事情也不做&#xff09;——阻塞式等待
pid_t ret&#61;waitpid(id,&status,0); //阻塞式等待
#include
#include
#include
#include
#include
int main()
pid_t id &#61;fork();
if(id&#61;&#61;0)
int count&#61;3;
while(count)
printf("child is running:%d,ppid:%d,pid:%d\\n",count--,getppid(),getpid());
sleep(1);
printf("child quit....\\n");
exit(0);
int status&#61;0;
pid_t ret&#61;waitpid(id,&status,0);
if(ret>0)
printf("wait success!\\n");
if(WIFEXITED(status))
printf("normal quit!\\n");
printf("quit code:%d\\n",WEXITSTATUS(status));
else
printf("process quit error!\\n");
else if(ret&#61;&#61;0)
printf("no process to wait\\n");
else
printf("wait failed\\n");
return 1;
return 0;
注&#xff1a;
当父进程调用waitpid函数时&#xff0c;父进程每隔一段时间会询问子进程的状态&#xff0c;如果父进程等待失败&#xff0c;父进程会往复的等待&#xff0c;直到父进程等待子进程退出并返回该子进程的pid为止&#xff0c;在等待失败到重新询问子进程的期间里&#xff0c;父进程会做一些其他事情——非阻塞式等待
pid_t ret &#61; waitpid(id,&status,WNOHANG); //非阻塞式等待
注&#xff1a;
补&#xff1a;
创建子进程的目的&#xff1a;
进程程序替换——当父进程创建出子进程让子进程执行其他程序的代码&#xff0c;这时该程序会从磁盘中将代码和数据直接加载到物理内存中&#xff0c;将子进程的页表由指向父进程的代码和数据指向到该程序的代码和数据
注&#xff1a;
替换原理&#xff1a;
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变
int execl(const char *path, const char *arg, ...);
注&#xff1a;
补&#xff1a;
int execlp(const char *file, const char *arg, ...);
注&#xff1a;一般环境变量PATH&#xff0c;都是一些系统命令才能在PATH中找到&#xff0c;或者还可以把自己的命令导入到PATH中
Linux下的环境变量与命令行参数
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execle(const char *path, const char *arg, ..., char * const envp[]);
int execve(const char *filename, char *const argv[],char *const envp[]);
补&#xff1a;
int execvpe(const char *file, char *const argv[], char *const envp[]);
注&#xff1a;
这六个exec系列函数的函数名都以exec开头&#xff0c;其后缀的含义如下&#xff1a;
事实上,只有execve是真正的系统调用,其它五个函数最终都调用 execve,所以execve在man手册 第2节,其它函数在man手册第3节。换句话说execve是其余六个函数的底层实现&#xff0c;只不过是改变了其余函数的调用方式&#xff08;参数&#xff09;
exec*函数之间的关系&#xff1a;
补充&#xff1a;
用下图的时间轴来表示事件的发生次序。其中时间从左向右。shell由标识为sh的方块代表&#xff0c;它随着时间的流逝从左向右移动。shell从用户读入字符串"ls"。shell建立一个新的进程&#xff0c;然后在那个进程中运行ls程序并等待那个进程结束。然后shell读取新的一行输入&#xff0c;建立一个新的进程&#xff0c;在这个进程中运行程序 并等待这个进程结束。
写一个shell&#xff0c;需要循环以下过程:
- 获取命令行
- 解析命令行
- 建立一个子进程&#xff08;fork&#xff09;
- 替换子进程&#xff08;execvp&#xff09;
- 父进程等待子进程退出&#xff08;waitpid&#xff09;
注&#xff1a;
代码如下&#xff1a;
#include
#include
#include
#include
#include
#include
#define NUM 128
#define SIZE 32
char command_line[NUM];
char *command_parse[SIZE];
int main()
while(1)
memset(command_line,&#39;\\0&#39;,sizeof(command_line));
printf("[lc&#64;myhost my_shell]$ ");
fflush(stdout);
//数据读取
if(fgets(command_line,NUM-1,stdin))
command_line[strlen(command_line)-1]&#61;&#39;\\0&#39;;
//字符串&#xff08;命令行数据分析&#xff09;
int index&#61;0;
command_parse[index]&#61;strtok(command_line," ");
while(1)
index&#43;&#43;;
command_parse[index]&#61;strtok(NULL," ");
if(command_parse[index]&#61;&#61;NULL)
break;
var cpro_id = "u6885494";